import React, {
  forwardRef,
  ReactElement,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState
} from 'react';
import { View } from 'react-native';
import Sound from 'react-native-sound';

import {
  ignoreDownloadTask,
  onEnd,
  onProgress,
  setTotalTime
} from '../../actions';
import ClassRoomVideo from './components/ClassRoomVideo';
import { Progress } from '../../../../components/MediaPlayer/index.d';
import ProblemTimer from '../ProblemTimer';
import styles from './style';
import { InstantAnswer, RoundInfo } from '../../../../api/class/getRoundList';
import Log from '../../../../common/log';
import { TeacherAudio } from '../../../../api/class/getPlatformStudent';
import { RollCallUserInfo } from '../../types';
import { prepareSoundWithUrl } from '../../../../util/sound';
import { emitRollCallImpromptu } from '../../event';
import { VideoType } from '../../constant';
import Loading from './components/Loading';
import customAwait from '../../../../util/customAwait';

interface VideoProps {
  url: string;
  audio?: TeacherAudio;
  instantAnswer?: InstantAnswer;
  rollCallUserInfo?: RollCallUserInfo;
  videoType?: VideoType;
  lessonId: number;
}

enum VideoEndType {
  Unknown,
  Video,
  Instant,
  SoundEnd
}

const soundPlayer: { [key: string]: Sound } = {};
const startStatus: { [key: string]: boolean } = {};

const Video = (
  {
    url,
    audio,
    instantAnswer,
    rollCallUserInfo,
    videoType,
    lessonId
  }: VideoProps,
  ref: any
): ReactElement => {
  const [restTime, setRestTime] = useState(0);
  const [isStart, setIsStart] = useState(false);
  const [showLoading, setShowLoading] = useState(false);
  const [localInstantAnswer, setLocalInstantAnswer] = useState<InstantAnswer>();
  const localTotalDuration = useRef(0);
  const haveSubmitInstant = useRef<boolean>(false);
  const haveEndPlaySound = useRef<boolean>(false);

  const videoPlayer = useRef<any>(null);
  const timer = useRef<any>(null);
  const isPause = useRef<boolean>(false);

  const isMute =
    videoType === VideoType.RollCall ||
    videoType === VideoType.SubmitRight ||
    videoType === VideoType.SubmitWrong;

  const noRollCallInfo =
    rollCallUserInfo && Number(rollCallUserInfo.userId) === 0;

  useEffect((): void => {
    if (noRollCallInfo && isStart) {
      setShowLoading(true);
    } else {
      setShowLoading(false);
    }
  }, [isStart, noRollCallInfo, rollCallUserInfo]);

  useEffect((): (() => void) => {
    return (): void => {
      if (soundPlayer[`${url}-${lessonId}`]) {
        soundPlayer[`${url}-${lessonId}`].release();
        soundPlayer[`${url}-${lessonId}`] = undefined;
      }
    };
  }, []);

  useEffect((): void => {
    const initAudio = async (): Promise<void> => {
      if (soundPlayer[`${url}-${lessonId}`]) return;
      if (audio && audio.url) {
        const [error, res] = await customAwait(prepareSoundWithUrl(audio.url));
        if (!error && res) {
          soundPlayer[`${url}-${lessonId}`] = res;
        }
      }
    };
    initAudio();
  }, [audio, lessonId, url]);

  const seek = async (time: number, needIgnore = true): Promise<void> => {
    if (videoPlayer && videoPlayer.current) {
      if (needIgnore) {
        ignoreDownloadTask(url, time);
      }
      await videoPlayer.current.seek(time);
      if (
        localTotalDuration.current > 0 &&
        localTotalDuration.current - time > 0
      ) {
        setRestTime(localTotalDuration.current - time);
      }
    }
  };

  const prepare = async (): Promise<void> => {
    if (videoPlayer && videoPlayer.current) {
      const totalTime = await videoPlayer.current.prepare();
      if (
        audio &&
        audio.url &&
        soundPlayer[`${url}-${lessonId}`] === undefined
      ) {
        const [error, res] = await customAwait(prepareSoundWithUrl(audio.url));
        if (!error && res) {
          soundPlayer[`${url}-${lessonId}`] = res;
        }
      }
      setTotalTime(totalTime);
    }
  };

  const onVideoEnd = useCallback(
    (endType: VideoEndType = VideoEndType.Video): void => {
      if (isPause.current) return;
      if (!startStatus[`${url}-${lessonId}`]) return;
      Log.i(
        'ClassRoom Video',
        JSON.stringify({ event: 'onVideoEnd', endType, url })
      );
      onEnd();
    },
    [lessonId, url]
  );

  const pause = async (): Promise<void> => {
    if (videoPlayer && videoPlayer.current) {
      isPause.current = true;
      if (timer && timer.current) {
        timer.current.pause();
      }
      await videoPlayer.current.pause();
      if (soundPlayer[`${url}-${lessonId}`]) {
        soundPlayer[`${url}-${lessonId}`].pause();
      }
    }
  };

  const playSound = async (progress: number = 0): Promise<void> => {
    if (
      soundPlayer[`${url}-${lessonId}`] &&
      soundPlayer[`${url}-${lessonId}`].getDuration() * 1000 > progress
    ) {
      if (progress) {
        await soundPlayer[`${url}-${lessonId}`].setCurrentTime(progress / 1000);
      }
      soundPlayer[`${url}-${lessonId}`].play((success): void => {
        if (soundPlayer[`${url}-${lessonId}`]) {
          if (
            videoType === VideoType.SubmitRight ||
            videoType === VideoType.SubmitWrong
          ) {
            onVideoEnd(VideoEndType.SoundEnd);
          }
          if (videoType === VideoType.RollCall && haveSubmitInstant.current) {
            onVideoEnd(VideoEndType.SoundEnd);
          }
          haveEndPlaySound.current = true;
          soundPlayer[`${url}-${lessonId}`].release();
          soundPlayer[`${url}-${lessonId}`] = undefined;
        }
      });
    } else {
      haveEndPlaySound.current = true;
    }
  };

  const start = async (round: RoundInfo, mute = false): Promise<void> => {
    if (videoPlayer && videoPlayer.current) {
      let progress = 0;
      let rest = false;
      let totalDuration = 0;
      if (round && round.roundInfoVideo) {
        progress = round.roundInfoVideo.roundProgress || 0;
        rest = round.roundInfoVideo.isRest;
        totalDuration = round.duration;
      } else if (round && round.roundInfoProblem) {
        progress = round.roundInfoProblem.explainVideoProgress || 0;
      } else if (round && round.roundInfoRollCall.explainVideoProgress) {
        progress = round.roundInfoRollCall.explainVideoProgress || 0;
      } else if (round && round.roundInfoRollCall.doProblemProgress) {
        progress = round.roundInfoRollCall.doProblemProgress || 0;
      }

      ignoreDownloadTask(url, progress);

      await prepare();
      setIsStart(true);
      startStatus[`${url}-${lessonId}`] = true;

      if (rest) {
        localTotalDuration.current = totalDuration;
        setRestTime(totalDuration - progress);
      }
      await videoPlayer.current.start(mute || isMute);

      if (noRollCallInfo) {
        setTimeout((): void => {
          pause();
        }, 1000);
        return;
      }
      await playSound(progress);
      if (progress) {
        await seek(progress, false);
      }
    }
  };

  const play = async (mute = false): Promise<void> => {
    if (videoPlayer && videoPlayer.current) {
      isPause.current = false;
      if (timer && timer.current) {
        timer.current.start();
      }
      await videoPlayer.current.start(mute || isMute);
      await playSound();
    }
  };

  const videoMute = (mute = false): void => {
    if (videoPlayer && videoPlayer.current) {
      videoPlayer.current.mute(mute || isMute);
    }
    if (soundPlayer[`${url}-${lessonId}`]) {
      if (mute) {
        soundPlayer[`${url}-${lessonId}`].setVolume(0);
      } else {
        soundPlayer[`${url}-${lessonId}`].setVolume(1);
      }
    }
  };

  const hideInstantAnswer = useCallback((): void => {
    if (instantAnswer) {
      emitRollCallImpromptu({
        ...instantAnswer,
        show: false
      });
    }
  }, [instantAnswer]);

  const destroy = async (): Promise<void> => {
    setIsStart(false);
    startStatus[`${url}-${lessonId}`] = false;
    isPause.current = false;
    setRestTime(0);
    hideInstantAnswer();
    localTotalDuration.current = 0;
    setLocalInstantAnswer(undefined);
    haveEndPlaySound.current = false;
    haveSubmitInstant.current = false;
    videoPlayer.current.destroy();
  };

  useImperativeHandle(ref, (): { [key: string]: any } => {
    return {
      prepare,
      start,
      pause,
      seek,
      destroy,
      play,
      mute: videoMute
    };
  });

  const updateImpromptuInfo = useCallback(
    (currentTime: number): void => {
      if (!instantAnswer) return;
      if (localInstantAnswer) return;
      const time = currentTime - instantAnswer.startTime;
      if (time > 0) {
        let duration =
          Math.floor((instantAnswer.duration - time) / 1000) * 1000;
        if (duration < 0) {
          duration = 1000;
        }
        emitRollCallImpromptu({
          ...instantAnswer,
          duration,
          hideInstantAnswer: (): void => {
            if (haveEndPlaySound.current) {
              onVideoEnd(VideoEndType.Instant);
            }
            haveSubmitInstant.current = true;
            hideInstantAnswer();
          },
          ...rollCallUserInfo,
          show: true
        });
        setLocalInstantAnswer({
          ...instantAnswer,
          duration
        });
      }
    },
    [
      hideInstantAnswer,
      instantAnswer,
      localInstantAnswer,
      onVideoEnd,
      rollCallUserInfo
    ]
  );

  const onVideoProgress = useCallback(
    (e: Progress): void => {
      if (isPause.current) return;
      if (!isStart) return;
      onProgress(e);
      if (!noRollCallInfo) {
        updateImpromptuInfo(e.currentTime);
      }
    },
    [isStart, noRollCallInfo, updateImpromptuInfo]
  );

  return useMemo((): ReactElement => {
    return (
      <View style={styles.container}>
        <View style={styles.content}>
          <ClassRoomVideo
            url={url}
            ref={videoPlayer}
            onProgress={onVideoProgress}
            onEnd={onVideoEnd}
          />
          {isStart && restTime > 0 && (
            <ProblemTimer
              key={restTime}
              ref={timer}
              title="课间倒计时"
              action={(): void => {}}
              time={restTime}
            />
          )}
          {showLoading && (
            <View style={styles.loading}>
              <Loading />
            </View>
          )}
        </View>
      </View>
    );
  }, [isStart, onVideoEnd, onVideoProgress, restTime, showLoading, url]);
};

export default forwardRef(Video);
